#include <cassert>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <random>

namespace Attr {
	void func()
	{
		int y[3]{0};
		// y [[]{ return 0; }()] = 1;  // error
		int i [[cats::meow([[]] )]] {0}; // OK
	}

	[[gnu::always_inline]] [[gnu::hot]] [[gnu::const]] [[nodiscard]]
		inline int f(); // declare f with four attributes

	[[gnu::always_inline, gnu::const, gnu::hot, nodiscard]]
	int f(); // same as above, but uses a single attr specifier that contains four attributes

	// C++17:
	[[using gnu:const, always_inline, hot]] [[nodiscard]]
		int f [[gnu::always_inline]] (); // an attribute may appear in multiple specifiers

	int f() { return 0; }
	 
}


namespace Attr_nodiscard
{
	struct [[nodiscard]] error_info { /*...*/ };
	error_info enable_missile_safety_mode() { /*...*/ return {}; }
	void launch_missiles() { /*...*/ }
	void test_missiles() {
		enable_missile_safety_mode(); // compiler may warn on discarding a nodiscard value
		launch_missiles();
	}
	error_info& foo() { static error_info e; /*...*/ return e; }
	void f1() {
		foo(); // nodiscard type is not returned by value, no warning
	}
#if ((__cplusplus > 201703L) || (_MSVC_LANG > 201703L ))
	// nodiscard( string-literal ) (since C++20):
	[[nodiscard("PURE FUN")]] int strategic_value(int x, int y) { return x ^ y; }
#endif
	int test()
	{
		strategic_value(4, 2); // compiler may warn on discarding a nodiscard value
		auto z = strategic_value(0, 0); // ok: return value is not discarded
		return z;
	}
}

namespace Attr_noreturn
{
	[[ noreturn ]] void f() {
		throw "error";
		// OK
	}

	void q [[ noreturn ]] (int i) {
		// behavior is undefined if called with an argument <= 0
		if (i > 0) {
			throw "positive";
		}
	}

	// void h() [[noreturn]]; // error: attribute applied to function type of h, not h itself
	void test() {
		try { f(); }
		catch (...) {}
		try { q(42); }
		catch (...) {}
	}

}

namespace Attr_fallthrough
{
	void g() {}
	void h() {}
	void i() {}
	void test(int n)
	{ 
		switch (n)
		{
		case 1:
		case 2:
			g();
			[[fallthrough]];
		case 3: // no warning on fallthrough
			h();
		case 4: // compiler may warn on fallthrough
			if (n < 3)
			{
				i();
				[[fallthrough]]; // OK
			}
			else
			{
				return;
			}
		case 5:
			while (false)
			{
				[[fallthrough]]; // ill-formed: next statement is not
				//             part of the same iteration
			}
		case 6:
			[[fallthrough]]; // ill-formed, no subsequent case or default label
		}
	}
}

namespace Attr_deprecated
{
	[[deprecated]]
	void TriassicPeriod() {
		std::clog << "Triassic Period: [251.9 - 208.5] million years ago.\n";
	}

	[[deprecated("Use NeogenePeriod() instead.")]]
	void JurassicPeriod() {
		std::clog << "Jurassic Period: [201.3 - 152.1] million years ago.\n";
	}

	[[deprecated("Use calcSomethingDifferently(int).")]]
	int calcSomething(int x) {
		return x * 2;
	}

	void test()
	{
		TriassicPeriod();
		JurassicPeriod();
		calcSomething(2);
	}
}

namespace Attr_maybe_unused
{
	[[maybe_unused]] void f([[maybe_unused]] bool thing1,
		[[maybe_unused]] bool thing2)
	{
		[[maybe_unused]] bool b = thing1 && thing2;
		assert(b); // in release mode, assert is compiled out, and b is unused
		// no warning because it is declared [[maybe_unused]]
	} // parameters thing1 and thing2 are not used, no warning

	void test()
	{

	}
}

namespace Attr_likely_unlikely {
	namespace with_attributes {
		constexpr double pow(double x, long long n) noexcept {
			if (n > 0) [[likely]]
				return x * pow(x, n - 1);
			else [[unlikely]]
				return 1;
		}
		constexpr long long fact(long long n) noexcept {
			if (n > 1) [[likely]]
				return n * fact(n - 1);
			else [[unlikely]]
				return 1;
		}
		constexpr double cos(double x) noexcept {
			constexpr long long precision{ 16LL };
			double y{};
			for (auto n{ 0LL }; n < precision; n += 2LL) [[likely]]
				y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n));
			return y;
		}
	}  // namespace with_attributes

	namespace no_attributes {
		constexpr double pow(double x, long long n) noexcept {
			if (n > 0)
				return x * pow(x, n - 1);
			else
				return 1;
		}
		constexpr long long fact(long long n) noexcept {
			if (n > 1)
				return n * fact(n - 1);
			else
				return 1;
		}
		constexpr double cos(double x) noexcept {
			constexpr long long precision{ 16LL };
			double y{};
			for (auto n{ 0LL }; n < precision; n += 2LL)
				y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n));
			return y;
		}
	}  // namespace no_attributes

	double gen_random() noexcept {
		static std::random_device rd;
		static std::mt19937 gen(rd());
		static std::uniform_real_distribution<double> dis(-1.0, 1.0);
		return dis(gen);
	}
	volatile double sink{}; // ensures a side effect

	void  test()
	{
		for (const auto x : { 0.125, 0.25, 0.5, 1. / (1 << 26) }) {
			std::cout
				<< std::setprecision(53)
				<< "x = " << x << '\n'
				<< std::cos(x) << '\n'
				<< with_attributes::cos(x) << '\n'
				<< (std::cos(x) == with_attributes::cos(x) ? "equal" : "differ") << '\n';
		}

		auto benchmark = [](auto fun, auto rem) {
			const auto start = std::chrono::high_resolution_clock::now();
			for (auto size{ 1ULL }; size != 10'000'000ULL; ++size) {
				sink = fun(gen_random());
			}
			const std::chrono::duration<double> diff =
				std::chrono::high_resolution_clock::now() - start;
			std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count()
				<< " sec " << rem << std::endl;
		};

		benchmark(with_attributes::cos, "(with attributes)");
		benchmark(no_attributes::cos, "(without attributes)");
		benchmark([](double t) { return std::cos(t); }, "(std::cos)");
	}
}

namespace Attr_no_unique_address {
	struct Empty {}; // empty class

	struct X {
		int i;
		Empty e;
	};

	struct Y {
		int i;
		[[no_unique_address]] Empty e;
	};

	struct Z {
		char c;
		[[no_unique_address]] Empty e1, e2;
	};

	struct W {
		char c[2];
		[[no_unique_address]] Empty e1, e2;
	};

	void test()
	{
		// the size of any object of empty class type is at least 1
		static_assert(sizeof(Empty) >= 1);

		// at least one more byte is needed to give e a unique address
		static_assert(sizeof(X) >= sizeof(int) + 1);

		// empty member optimized out
		std::cout << "sizeof(Y) == sizeof(int) is " << std::boolalpha
			<< (sizeof(Y) == sizeof(int)) << '\n';

		// e1 and e2 cannot share the same address because they have the
		// same type, even though they are marked with [[no_unique_address]]. 
		// However, either may share address with c.
		static_assert(sizeof(Z) >= 2);

		// e1 and e2 cannot have the same address, but one of them can share with
		// c[0] and the other with c[1]
		std::cout << "sizeof(W) == 2 is " << (sizeof(W) == 2) << '\n';
	}
}

namespace Attr_assume
{
#if ((__cplusplus > 202002L) || (_MSVC_LANG > 202002L ))
	void test(int& x, int y)
	{
		void g(int);
		void h();

		[[assume(x > 0)]]; // Compiler may assume x is positive

		g(x / 2); // More efficient code possibly generated

		x = 3;

		int z = x;

		[[assume((h(), x == z))]]; // Compiler may assume x would have the same value after
		// calling h
		// The assumption does not cause a call to h

		h();

		g(x); // Compiler may replace this with g(3);

		h();

		g(x); // Compiler may NOT replace this with g(3);
		// An assumption applies only at the point where it appears

		z = std::abs(y);

		[[assume((g(z), true))]]; // Compiler may assume g(z) will return

		g(z); // Due to above and below assumptions, compiler may replace this with g(10);

		[[assume(y == -10)]]; // Undefined behavior if y != -10 at this point

		[[assume((x - 1) * 3 == 12]];

		g(x); // Compiler may replace this with g(5);
	} 
#endif
}

int main(int argc, char* argv[])
{
	Attr_deprecated::test();
	return 0;
}
